using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using JTLeigh.Tools.Development.CopySourceAsHtml.Properties;

namespace JTLeigh.Tools.Development.CopySourceAsHtml
{

	/// <summary>
	/// Copies RTF as HTML.
	/// </summary>
    public sealed class Copier : RtfReader
    {
		#region Static Methods

		/// <summary>
		/// Gets the file style.
		/// </summary>
		/// <param name="fontList">The font list.</param>
		/// <param name="fontSize">The font size.</param>
		/// <param name="plainTextStyle">The plain text style.</param>
		/// <param name="fileStyleOverrides">Additional CSS elements to append to the file style.</param>
		/// <returns>The file style.</returns>
		[SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "plainText")]
		public static ColorableItemStyle CreateFileStyle(string fontList, int fontSize, ColorableItemStyle plainTextStyle, string fileStyleOverrides)
		{
			ColorableItemStyle fileStyle;

			Safety.AssertArgumentNotNull("fontList", fontList);
			Safety.AssertArgumentNotNull("plainTextStyle", plainTextStyle);
			// fileStyleOverrides can be null.

			fileStyle = new ColorableItemStyle(plainTextStyle.ForegroundColor, plainTextStyle.BackgroundColor, plainTextStyle.Bold);
			fileStyle.AppendFormat("font-family: {0};", fontList);
			fileStyle.AppendFormat("font-size: {0}pt;", fontSize);
			if (fileStyleOverrides != null)
			{
				fileStyle.Append(fileStyleOverrides);
			}
			return fileStyle;
		}

		/// <summary>
		/// Gets the line style.
		/// </summary>
		/// <returns>The line style.</returns>
		public static ColorableItemStyle CreateLineStyle(ColorableItemStyle plainTextStyle, string lineStyleOverrides)
		{
			ColorableItemStyle lineStyle;

			// lineStyleOverrides can be null.

			lineStyle = new ColorableItemStyle(plainTextStyle.ForegroundColor, plainTextStyle.BackgroundColor, plainTextStyle.Bold, plainTextStyle);
			lineStyle.Append("margin: 0px;");
			if (lineStyleOverrides != null)
			{
				lineStyle.Append(lineStyleOverrides);
			}
			return lineStyle;
		}

		/// <summary>
		/// Gets the alternate line number style.
		/// </summary>
		/// <returns>The alternate line number style.</returns>
		public static ColorableItemStyle CreateAlternateLineNumberStyle(ColorableItemStyle lineNumberStyle, double adjustment, ColorableItemStyle lineStyle, string lineNumberStyleOverrides)
		{
			ColorableItemStyle alternateLineNumberStyle;

			// lineNumberStyleOverrides can be null.

			alternateLineNumberStyle = new ColorableItemStyle(lineNumberStyle.ForegroundColor, HtmlColor.AdjustColor(lineNumberStyle.BackgroundColor, adjustment), lineNumberStyle.Bold, lineStyle);
			if (lineNumberStyleOverrides != null)
			{
				alternateLineNumberStyle.Append(lineNumberStyleOverrides);
			}

			return alternateLineNumberStyle;
		}

		/// <summary>
		/// Gets the alternate line style.
		/// </summary>
		/// <returns>The alternate line style.</returns>
		public static ColorableItemStyle CreateAlternateLineStyle(ColorableItemStyle plainTextStyle, double adjustment, string lineStyleOverrides)
		{
			ColorableItemStyle alternateLineStyle;

			// lineStyleOverrides can be null.

			alternateLineStyle = new ColorableItemStyle(plainTextStyle.ForegroundColor, HtmlColor.AdjustColor(plainTextStyle.BackgroundColor, adjustment), plainTextStyle.Bold, plainTextStyle);
			alternateLineStyle.Append("margin: 0px;");
			if (lineStyleOverrides != null)
			{
				alternateLineStyle.Append(lineStyleOverrides);
			}
			return alternateLineStyle;
		}

		public static ColorableItemStyle CreateFileNameStyle(ColorableItemStyle plainTextStyle, string fileNameStyleOverrides)
		{
			ColorableItemStyle fileNameStyle;

			// fileNameStyleOverrides can be null.

			fileNameStyle = new ColorableItemStyle(plainTextStyle.ForegroundColor, plainTextStyle.BackgroundColor, plainTextStyle.Bold, plainTextStyle);
			fileNameStyle.Append("margin: 0px;");
			if (fileNameStyleOverrides != null)
			{
				fileNameStyle.Append(fileNameStyleOverrides);
			}
			return fileNameStyle;
		}

		#endregion // Static Methods

		#region Fields

        private ICopierSettings _settings;
        private ColorableItemStyle _plainTextStyle;
        private ColorableItemStyle _lineNumberStyle;
		private ColorableItemStyle _alternateLineNumberStyle;
		private ColorableItemStyle _fileStyle;
		private ColorableItemStyle _lineStyle;
		private ColorableItemStyle _alternateLineStyle;
		private ColorableItemStyle _fileNameStyle;
		private Dictionary<int, HtmlColor> _htmlColors;
        private HtmlFile _htmlFile;

        #endregion // Fields

        #region Constructors

		/// <summary>
		/// Initializes a new instance of the <see cref="Copier"/> class with the specified 
		/// settings, plain text style, and line number style.
		/// </summary>
		/// <param name="settings">The copier settings.</param>
		/// <param name="plainTextStyle">The plain text style.</param>
		/// <param name="lineNumberStyle">The line number style.</param>
		[SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "plainText")]
		public Copier(ICopierSettings settings, ColorableItemStyle plainTextStyle, ColorableItemStyle lineNumberStyle)
        {
			Safety.AssertArgumentNotNull("settings", settings);
			Safety.AssertArgumentNotNull("plainTextStyle", plainTextStyle);
			Safety.AssertArgumentNotNull("lineNumberStyle", lineNumberStyle);

            _settings = settings;
            _plainTextStyle = plainTextStyle;
            _lineNumberStyle = lineNumberStyle;
			_fileStyle = Copier.CreateFileStyle(_settings.FontList, _settings.FontSize, _plainTextStyle, _settings.FileStyle);

			_lineStyle = Copier.CreateLineStyle(_plainTextStyle, _settings.LineStyle);
			if (_settings.AlternateBackgroundColors)
			{
				_alternateLineStyle = CreateAlternateLineStyle(_plainTextStyle, _settings.AlternateBackgroundColorAdjustment, _settings.LineStyle);
				_alternateLineNumberStyle = CreateAlternateLineNumberStyle(_lineNumberStyle, _settings.AlternateBackgroundColorAdjustment, _alternateLineStyle, _settings.LineNumberStyle);
			}

			if (_settings.CopyFileName)
			{
				_fileNameStyle = CreateFileNameStyle(_plainTextStyle, _settings.FileNameStyle);
			}

		}

        #endregion // Constructors

        #region Methods

		/// <summary>
		/// Copies the specified RTF as HTML using the settings, plain text style, and line number
		/// style passed to the constructor.
		/// </summary>
		/// <param name="path">The path to the file being copied, either relative or absolute,
		/// depending on the copying settings.</param>
		/// <param name="rtf">The RTF to copy.</param>
		/// <returns>The resulting HTML.</returns>
        public string CopyAsHtml(string path, string rtf)
        {

            int outdentSpaces;
            StringBuilder html;
			HtmlStyleSheet styleSheet = null;
            HtmlStyle blockStyle;
            string content;
            int lineIndex;
            int lineNumber;
			ColorableItemStyle currentLineNumberStyle;
			string currentLineNumberStyleName;
			HtmlStyle currentLineStyle;
			string currentLineStyleName;
			int outdentSpacesRemaining;
            bool allowLeadingSpace;
            HtmlColor foregroundColor;
			HtmlColor backgroundColor;

            Safety.AssertArgumentNotNull("rtf", rtf);

            // reset the color table and the file.

            _htmlColors = new Dictionary<int, HtmlColor>();
            _htmlFile = new HtmlFile();

            // read the rtf and process it into the file, lines, and blocks.

            Read(rtf);

            if (_settings.RemoveIndentation)
            {
				outdentSpaces = _htmlFile.IndentationSpaces;
            }
            else
            {
                outdentSpaces = 0;
            }

			if (!_settings.EmbedStyles)
            {
				styleSheet = new HtmlStyleSheet();
				styleSheet.AddStyle("cf", _fileStyle);
				styleSheet.AddStyle("cl", _lineStyle);
				if (_settings.AlternateBackgroundColors)
				{
					styleSheet.AddStyle("cal", _alternateLineStyle);
				}
                if (_settings.NumberLines)
				{
					if (_lineNumberStyle.Length > 0)
					{
						styleSheet.AddStyle("cln", _lineNumberStyle);
					}
					if (_settings.AlternateBackgroundColors && (_alternateLineNumberStyle.Length > 0))
					{
						styleSheet.AddStyle("caln", _alternateLineNumberStyle);
					}
				}
				if (_settings.CopyFileName)
				{
					styleSheet.AddStyle("cfn", _fileNameStyle);
				}
            }

            // build up the output string.

            html = new StringBuilder();

			// append the beginning of the file.
            html.Append("<div");
            if (_settings.EmbedStyles)
            {
                html.AppendFormat(" style=\"{0}\"", _fileStyle.ToString());
            }
            else
            {
                html.Append(" class=\"cf\"");
            }
            html.Append(">");
            if (!_settings.StripLineBreaks)
            {
                html.Append("\r\n");
            }

			// append the filename.
			if (_settings.CopyFileName)
			{

				// append the beginning of the line.
				if (_settings.WrapWords)
				{
					html.Append("<p");
				}
				else
				{
					html.Append("<pre");
				}
				if (_fileNameStyle.Length > 0)
				{
					if (_settings.EmbedStyles)
					{
						html.AppendFormat(" style=\"{0}\"", _fileNameStyle.ToString());
					}
					else
					{
						html.Append(" class=\"cfn\"");
					}
				}
				html.Append(">");
	
				// append the filename.
				html.Append(HtmlTools.HtmlEncode(path));

				// append the end of the line.
				if (_settings.WrapWords)
				{
					html.Append("</p>");
				}
				else
				{
					html.Append("</pre>");
				}
				if (!_settings.StripLineBreaks)
				{
					html.Append("\r\n");
				}

			}


            // append the lines of file.
            lineIndex = 1;
            lineNumber = _settings.NumberLinesFrom;

			currentLineNumberStyle = _lineNumberStyle;
			currentLineNumberStyleName = "cln";
			currentLineStyle = _lineStyle;
			currentLineStyleName = "cl";

			foreach (HtmlLine line in _htmlFile.Lines)
            {

				if (_settings.AlternateBackgroundColors)
				{
					if ((lineIndex % 2) == 1)
					{
						currentLineNumberStyle = _lineNumberStyle;
						currentLineNumberStyleName = "cln";
						currentLineStyle = _lineStyle;
						currentLineStyleName = "cl";
					}
					else
					{
						currentLineNumberStyle = _alternateLineNumberStyle;
						currentLineNumberStyleName = "caln";
						currentLineStyle = _alternateLineStyle;
						currentLineStyleName = "cal";
					}
				}

                outdentSpacesRemaining = outdentSpaces;
                if (line.HasContent || (lineIndex != _htmlFile.Lines.Count))
                {

                    // append the beginning of the line.
                    if (_settings.WrapWords)
                    {
                        html.Append("<p");
                    }
                    else
                    {
                        html.Append("<pre");
                    }
                    if (_settings.EmbedStyles)
                    {
                        html.AppendFormat(" style=\"{0}\"", currentLineStyle.ToString());
                    }
                    else
                    {
                        html.AppendFormat(" class=\"{0}\"", currentLineStyleName);
                    }
                    html.Append(">");

                    // append the line number.
                    if (_settings.NumberLines)
                    {
						if (currentLineNumberStyle.Length > 0)
						{
							html.Append("<span");
							if (_settings.EmbedStyles)
							{
								html.AppendFormat(" style=\"{0}\"", currentLineNumberStyle.ToString());
							}
							else
							{
								html.AppendFormat(" class=\"{0}\"", currentLineNumberStyleName);
							}
							html.Append(">");
						}
                        html.Append(lineNumber.ToString(NumberFormatInfo.InvariantInfo).PadLeft(5).Replace(" ", "&nbsp;"));
						if (currentLineNumberStyle.Length > 0)
						{
							html.Append("</span>");
						}
                        html.Append("&nbsp;");
                    }

                    // append the line content.
                    if (line.HasContent)
                    {
                        allowLeadingSpace = false;
                        foreach (HtmlBlock block in line.Blocks)
                        {
                            if (block.HasContent || (block.LeadingSpaces > 0))
                            {
                                content = block.GetHtml(allowLeadingSpace, outdentSpacesRemaining, _settings.TabSize);
                                outdentSpacesRemaining -= block.LeadingSpaces;
                                if (outdentSpacesRemaining < 0)
                                {
                                    outdentSpacesRemaining = 0;
                                }
                                if (content.Length > 0)
                                {
                                    allowLeadingSpace = true;
                                    blockStyle = new HtmlStyle();
                                    if (_htmlColors.ContainsKey(block.ForegroundColor))
                                    {
                                        foregroundColor = _htmlColors[block.ForegroundColor];
                                        if (!foregroundColor.Equals(_plainTextStyle.ForegroundColor))
                                        {
                                            blockStyle.AppendFormat("color: {0};", foregroundColor);
                                        }
                                    }
                                    if (_htmlColors.ContainsKey(block.BackgroundColor))
                                    {
                                        backgroundColor = _htmlColors[block.BackgroundColor];
                                        if (!backgroundColor.Equals(_plainTextStyle.BackgroundColor))
                                        {
                                            blockStyle.AppendFormat("background: {0};", backgroundColor);
                                        }
                                    }
                                    if (block.Bold != _plainTextStyle.Bold)
                                    {
                                        if (block.Bold)
                                        {
                                            blockStyle.Append("font-weight: bold;");
                                        }
                                        else
                                        {
                                            blockStyle.Append("font-weight: normal;");
                                        }
                                    }
                                    if (!String.IsNullOrEmpty(_settings.BlockStyle))
                                    {
                                        blockStyle.Append(_settings.BlockStyle);
                                    }
                                    if (blockStyle.Length > 0)
                                    {
                                        html.Append("<span");
                                        if (_settings.EmbedStyles)
                                        {
                                            html.AppendFormat(" style=\"{0}\"", blockStyle.ToString());
                                        }
                                        else
                                        {
                                            html.AppendFormat(" class=\"{0}\"", styleSheet.AddReusableStyle("cb", blockStyle));
                                        }
                                        html.Append(">");
                                    }
                                    html.Append(content);
                                    if (blockStyle.Length > 0)
                                    {
                                        html.Append("</span>");
                                    }
                                }
                            }
                        }
                    }
                    else
                    {
                        if (!_settings.NumberLines)
                        {
                            html.Append("&nbsp;");
                        }
                    }

                    // append the end of the line.
                    if (_settings.WrapWords)
                    {
                        html.Append("</p>");
                    }
                    else
                    {
                        html.Append("</pre>");
                    }
                    if (!_settings.StripLineBreaks)
                    {
                        html.Append("\r\n");
                    }
                }

                lineIndex++;
                lineNumber++;

            }

            // append the end of the file.
            html.Append("</div>");
            if (!_settings.StripLineBreaks)
            {
                html.Append("\r\n");
            }

            // insert the stylesheet that was built up during the copy at the beginning of the
            // stream.
            if (!_settings.EmbedStyles)
            {
                html.Insert(0, styleSheet.GetHtml(_settings.StripLineBreaks));
            }

            return html.ToString();

        }

		/// <summary>
		/// Called when a color table entry is read.
		/// </summary>
		/// <param name="colorId">The color identifier.</param>
		/// <param name="color">The color.</param>
		protected internal override void ReadColorTableEntry(int colorId, Color color)
        {
            _htmlColors.Add(colorId, new HtmlColor(color.R, color.G, color.B));
        }

		/// <summary>
		/// Called when a foreground color change is read.
		/// </summary>
		/// <param name="colorId">The new foreground color identifier.</param>
		protected internal override void ReadForegroundColor(int colorId)
        {
            _htmlFile.SetForegroundColor(colorId);
        }

		/// <summary>
		/// Called when a background color change is read.
		/// </summary>
		/// <param name="colorId">The new background color identifier.</param>
		protected internal override void ReadBackgroundColor(int colorId)
        {
            _htmlFile.SetBackgroundColor(colorId);
        }

		/// <summary>
		/// Called when a bold change is read.
		/// </summary>
		/// <param name="bold"><see langword="true"/> if bold is being turned on, 
		/// <see langword="false"/> if bold is being turned off.</param>
		protected internal override void ReadBold(bool bold)
        {
            _htmlFile.SetBold(bold);
        }

		/// <summary>
		/// Called when a newline is read.
		/// </summary>
		protected internal override void ReadNewLine()
        {
            _htmlFile.StartNewLine();
        }

		/// <summary>
		/// Called when a character is read.
		/// </summary>
		/// <param name="character">The character.</param>
		/// <remarks>
		/// <para>
		/// This method is called for every printable character, including spaces (' ') and tabs 
		/// ('\t'), but not for newlines.  The <see cref="ReadNewLine"/> method is called for every
		/// newline.
		/// </para>
		/// </remarks>
		protected internal override void ReadCharacter(char character)
        {
            int space;

            if (character == '\t')
            {
                // FIXME: This isn't correct.  Instead of appending .TabSize spaces, we should be
                // figuring out how many spaces we need to add to move to the next tabstop, and
                // appending that many.  This will require a little extra work.
                for (space = 0; space < _settings.TabSize; space++)
                {
                    _htmlFile.Append(' ', _settings.TabSize);
                }
            }
            else
            {
                _htmlFile.Append(character, _settings.TabSize);
            }
        }

		/// <summary>
		/// Called when a group beginning is read.
		/// </summary>
		protected internal override void ReadBeginGroup()
        {
            _htmlFile.PushBlock();
        }

		/// <summary>
		/// Called when a group ending is read.
		/// </summary>
		protected internal override void ReadEndGroup()
        {
            _htmlFile.PopBlock();
        }

        #endregion // Methods
    }

}
